package org.df.ulab.bot.plugins.xmpp;

import java.io.File;
import java.util.Collection;

import org.df.ulab.bot.api.message.ImMessage;
import org.df.ulab.bot.api.message.ImStatus;
import org.df.ulab.bot.api.message.Message;
import org.df.ulab.bot.util.communication.BaseCommunicationService;
import org.df.ulab.bot.util.core.StorageSystem;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Mode;
import org.jivesoftware.smack.packet.Presence.Type;
import org.jivesoftware.smackx.filetransfer.FileTransferListener;
import org.jivesoftware.smackx.filetransfer.FileTransferManager;
import org.jivesoftware.smackx.filetransfer.FileTransferNegotiator;
import org.jivesoftware.smackx.filetransfer.FileTransferRequest;
import org.jivesoftware.smackx.filetransfer.IncomingFileTransfer;
import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @author Daniel Filgueiras
 * 04/05/2011
 */
public class XmppService extends BaseCommunicationService implements PacketListener, FileTransferListener, RosterListener {

	private static final Logger logger = LoggerFactory.getLogger(XmppService.class);
	
	private int		watchDog = 5;
	
	private String login;
	private String password;
	
	private XMPPConnection connection;
	private FileTransferManager manager;
	
	public XmppService() {
	}
	
	@Override
	public void run() {
		
		logger.info("XMPP Service thread running");
		XmppSettings settings = StorageSystem.getInstance().load(XmppSettings.class, "xmpp", true);
		
		login = settings.getLogin();
		password = settings.getPassword();
		
		if(login == null || password == null) {
			logger.error("Login and Password not set");
			return;
		}
		
		try {
			connection = new XMPPConnection(new ConnectionConfiguration(
					settings.getHost(), 
					settings.getPort(),
					settings.getService()));
			
			logger.info("XMPP Service started");
			while(keepAlive) {
				if (!(connection.isConnected() && connection.isAuthenticated())) {
					this.connect();
				}
				
				sendPendingMessages();
				
				Thread.sleep(watchDog * 1000);
			}
			logger.info("Service keep-alive set to false");
		} catch(InterruptedException e) {
			logger.warn("Connection interrupted");
		} catch(Exception e) {
			logger.warn("Ops!!! Something bad happen.",e);
		} finally {
			logger.debug("Closing connection...");
			if (connection != null && connection.isConnected()) {
				connection.disconnect();
			}
		}
		logger.debug("Service Stoped");
	}
	
	private void sendPendingMessages() {
		Message toSend = getNextMessage();
		while (toSend != null) {
			if (toSend instanceof XmppFileMessage) {
				XmppFileMessage fileMessage = (XmppFileMessage)toSend;
				sendFile(fileMessage);
			} else if (toSend instanceof XmppMessage) {
				XmppMessage xmppMessage = (XmppMessage)toSend;
				
				sendTextMessage(xmppMessage.getRemoteId(), xmppMessage.toText());
			} else {
				logger.warn("Unknow message type {}", toSend.getClass());
			}
			
			toSend = getNextMessage();
		}
	}
	
	private void sendTextMessage(String destination, StringBuilder message) {
		
		if (!destination.equals(login)) {
			logger.debug("Sending message to {}", destination);
			org.jivesoftware.smack.packet.Message.Type type = org.jivesoftware.smack.packet.Message.Type.chat;
			org.jivesoftware.smack.packet.Message msg = new org.jivesoftware.smack.packet.Message(destination, type);
			msg.setBody(message.toString());
			connection.sendPacket(msg);
		}
	}
	
	private boolean sendFile(XmppFileMessage fileMessage) {
		try {
			FileTransferManager manager = new FileTransferManager(connection);
			OutgoingFileTransfer transfer = manager.createOutgoingFileTransfer(fileMessage.getRemoteId());
			transfer.sendFile(fileMessage.getFile(), fileMessage.toString());
			
			for (int c = 0; c < 10 && !transfer.isDone(); c++) {
				logger.info("Tranfer status: {} / {} bytes", transfer.getStatus(), transfer.getBytesSent());
				Thread.sleep(1000);
			}
			return true;
		} catch (XMPPException e) {
			logger.error("Error sending file {}", fileMessage.getFile().getName(),e);
		} catch (InterruptedException e) {
			logger.error("Error sending file {}", fileMessage.getFile().getName(),e);
		}
		return false;
	}
	
	private boolean connect() {
		try {
			connection.disconnect();
		} catch (IllegalStateException e) {
			logger.warn("Unecessary disconnection",e);
		}
		try {
			connection.connect();
			connection.addPacketListener(this, null);
			connection.login(login, password);
			connection.getRoster().setSubscriptionMode(SubscriptionMode.manual);
			connection.getRoster().addRosterListener(this);
			connection.sendPacket(new Presence(Type.available, "Spike", 0, Mode.available));
			logger.info("Connected");
			
			FileTransferNegotiator.setServiceEnabled(connection, true);
			manager = new FileTransferManager(connection);
			manager.addFileTransferListener(this);
			return true;
		} catch (XMPPException e) {
			logger.warn("Connection failed",e);
		} catch (IllegalStateException e) {
			logger.warn("Unecessary connection",e);
		}
		return false;
	}
	
	@Override
	public boolean subscribe(String remoteId) {
		try {
			Presence packet = new Presence(Presence.Type.subscribed);
			packet.setTo(remoteId);
			packet.setFrom(login);
			connection.sendPacket(packet);
			return true;
		} catch (Exception e) {
			logger.warn("Subscribe packet failed",e);
		}
		return false;
	}
	
	@Override
	public boolean unsubscribe(String remoteId) {
		try {
			Presence packet = new Presence(Presence.Type.unsubscribed);
			packet.setTo(remoteId);
			packet.setFrom(login);
			connection.sendPacket(packet);
			return true;
		} catch (Exception e) {
			logger.warn("Subscribe packet failed",e);
		}
		return false;
	}
	
	public void reply(Message message, CharSequence alert) {
		if (message instanceof ImMessage) {
			String from = ((ImMessage)message).getRemoteId();
			sendTextMessage(from, new StringBuilder(alert));
		} else {
			logger.warn("Can not reply message type: {}", message.getClass());
		}
	}
	
	@Override
	public void reply(Message message, DefaultMsgs msg) {
		reply(message, msg.toString());
	}
	
	@Override
	public void processPacket(Packet packet) {
		String from = packet.getFrom().split("/")[0];
		
		if (from.equals(login)) {
			return;
		}
		
		RosterEntry rEntry = connection.getRoster().getEntry(from);
		if (rEntry != null) {
			logger.debug("Packet from roster entry: {}", rEntry.getUser());
		}
		
		if (packet instanceof Presence) {
			ImStatus status = null;
			Presence presence = (Presence)packet;
			switch(presence.getType()){
				case available:
					if (presence.getMode() == null)
						status = ImStatus.ONLINE_AVAILABLE;
					else {
						switch (presence.getMode()) {
						case away:
						case xa:
							status = ImStatus.ONLINE_AWAY;
							break;
						case dnd:
							status = ImStatus.ONLINE_BUSY;
							break;
						default:
							status = ImStatus.ONLINE_AVAILABLE;
							break;
						}
					}
					
					break;
				case unavailable:
					status = ImStatus.OFFLINE;
					break;
					
				case subscribe:
					status = ImStatus.SUBSCRIBE;
					break;
			}
			
			if (status != null) {
				addIncome(new XmppStatusMessage(from, status));
			}
			return;
		}
		
		if (packet instanceof org.jivesoftware.smack.packet.Message) {
			addIncome(new XmppMessage(from, ((org.jivesoftware.smack.packet.Message)packet).getBody() ));
			return;
	}
		
		logger.warn("Unknown packet type ({}): {}", packet.getClass(), packet);
	}
	
	@Override
	public void fileTransferRequest(FileTransferRequest request) {
		
		logger.info("Receiving file: {}",
				String.format("Receiving file: %1$c (%2$c) from %3$c (size: %4$d)", 
				request.getFileName(),
				request.getMimeType(),
				request.getRequestor(),
				request.getFileSize()));
		
		IncomingFileTransfer transfer = request.accept();
		File fRef = new File("resouces/" + request.getFileName());
		if (fRef.exists())
			fRef.delete();
		
		try {
			transfer.recieveFile(fRef);
		} catch (XMPPException e) {
			logger.warn("Tranfer failed",e);
		}
	}
	
	@Override
	public void entriesAdded(Collection<String> entries) {
		logger.debug("Roster / entries added");
	}
	
	public void entriesDeleted(Collection<String> entries) {
		logger.debug("Roster / entries deleted");
	}

	@Override
	public void entriesUpdated(Collection<String> entries) {
		logger.debug("Roster / entries updated");
	}
	
	@Override
	public void presenceChanged(Presence presence) {
		logger.debug("Roster / precense changed");
	}
}
